﻿using System;
using System.IO;
using System.Net;
using System.Web;
using System.Text;
using System.Xml.Serialization;
using Shipwire.Messages;

namespace Shipwire
{
    public class ShipwireService : IShipwireService
    {
        #region Private Const Attributes
        private const string c_ProductionUri = "https://api.shipwire.com/exec/";
        private const string c_SandboxUri = "https://api.beta.shipwire.com/exec/";
        private const string c_RequestAddress_Inventory = "InventoryServices.php";
        private const string c_RequestAddress_Rate = "RateServices.php";
        private const string c_RequestAddress_Fulfillment = "FulfillmentServices.php";
        private const string c_RequestAddress_Tracking = "TrackingServices.php";
        private const string c_RequestField_Inventory = "InventoryUpdateXML";
        private const string c_RequestField_Rate = "RateRequestXML";
        private const string c_RequestField_Fulfillment = "OrderListXML";
        private const string c_RequestField_Tracking = "TrackingUpdateXML";
        #endregion

        #region Protected Properties
        protected Enviroment Enviroment { get; set; }
        protected Server Server { get; set; }
        protected string EmailAddress { get; set; }
        protected string Password { get; set; }
        #endregion

        #region Constructors
        public ShipwireService(Enviroment enviroment, Server server, string email, string password)
        {
            Enviroment = enviroment;
            Server = server;
            EmailAddress = email;
            Password = password;
        }
        #endregion
        
        #region Inventory Operations
        public InventoryUpdateResponse GetInventoryDetails()
        {
            return GetInventoryDetails(null, null, false, new string[0]);
        }

        public InventoryUpdateResponse GetInventoryDetails(params string[] productCode)
        {
            return GetInventoryDetails(null, null, false, productCode);
        }

        public InventoryUpdateResponse GetInventoryDetails(Warehouse warehouse)
        {
            return GetInventoryDetails(warehouse, new string[0]);
        }
        
        public InventoryUpdateResponse GetInventoryDetails(Warehouse warehouse, params string[] productCode)
        {
            return GetInventoryDetails(null, warehouse, false, productCode);
        }

        public InventoryUpdateResponse GetInventoryDetails(WarehouseCountry? country, Warehouse? warehouse, bool includeEmpty, params string[] productCode)
        {
            InventoryUpdateRequest request = new InventoryUpdateRequest(Server, EmailAddress, Password)
            {
                Country = country,
                Warehouse = warehouse,
                ProductCode = productCode,
                IncludeEmpty = includeEmpty
            };
            return ExecuteSyncRequest<InventoryUpdateResponse>(c_RequestAddress_Inventory, c_RequestField_Inventory, request);
        }
        #endregion

        #region Rate Operations
        public RateResponse GetShippingEstimates(OrderRateRequest order)
        {
            order.AddressInfo.CleanForShipwire();

            RateRequest request = new RateRequest(Server, EmailAddress, Password) {Order = order};
            return ExecuteSyncRequest<RateResponse>(c_RequestAddress_Rate, c_RequestField_Rate, request);
        }
        #endregion

        #region Fulfillment Operations
        public FulfillmentResponse PlaceOrder(params FulfillmentOrder[] orders)
        {
            foreach(var order in orders)
                order.AddressInfo.CleanForShipwire();

            FulfillmentRequest request = new FulfillmentRequest(Server, EmailAddress, Password) {Orders = orders};
            return ExecuteSyncRequest<FulfillmentResponse>(c_RequestAddress_Fulfillment, c_RequestField_Fulfillment, request);
        }
        #endregion

        #region Tracking Operations
        public TrackingResponse GetAllTrackingData()
        {
            TrackingRequest request = new TrackingRequest(Server, EmailAddress, Password) { Bookmark = (int)Bookmark.AllOrders };
            return ExecuteSyncRequest<TrackingResponse>(c_RequestAddress_Tracking, c_RequestField_Tracking, request);
        }

        public TrackingResponse TrackCustomerOrder(string orderNo)
        {
            TrackingRequest request = new TrackingRequest(Server, EmailAddress, Password) { Bookmark = null, OrderNo = orderNo };
            return ExecuteSyncRequest<TrackingResponse>(c_RequestAddress_Tracking, c_RequestField_Tracking, request);
        }

        public TrackingResponse TrackShipwireOrder(string shipwireId)
        {
            TrackingRequest request = new TrackingRequest(Server, EmailAddress, Password) { Bookmark = null, ShipwireId = shipwireId };
            return ExecuteSyncRequest<TrackingResponse>(c_RequestAddress_Tracking, c_RequestField_Tracking, request);
        }
        #endregion

        #region Private Methods
        protected T ExecuteSyncRequest<T>(string actionAddress, string postField, object message)
        {
            HttpWebRequest request = CreateRequest(actionAddress);

            StringWriter xmlData = new StringWriter();
            XmlSerializer serializer = new XmlSerializer(message.GetType());
            serializer.Serialize(xmlData, message);

            StringWriter contentData = new StringWriter();
            contentData.Write(postField);
            contentData.Write("=");
            string xmlString = xmlData.GetStringBuilder().ToString();
            contentData.Write(HttpUtility.UrlEncode(xmlString));

            string contentString = contentData.GetStringBuilder().ToString();
            request.ContentLength = contentString.Length;
            request.ContentType = "application/x-www-form-urlencoded";
            request.Method = "POST";
            using (Stream requestStream = request.GetRequestStream())
            {
                requestStream.Write(ASCIIEncoding.ASCII.GetBytes(contentString), 0, ASCIIEncoding.ASCII.GetByteCount(contentString));
            }
            
            WebResponse response = request.GetResponse();
            byte[] responseData = new byte[response.ContentLength];
            using (Stream responseStream = response.GetResponseStream())
            {
                responseStream.Read(responseData, 0, responseData.Length);
            }
            string xmlResult = ASCIIEncoding.ASCII.GetString(responseData);
            serializer = new XmlSerializer(typeof(T));
            T responseObject = (T)serializer.Deserialize(new MemoryStream(responseData));
            return responseObject;
        }

        protected HttpWebRequest CreateRequest(string actionAddress)
        {
            Uri baseUri;
            if(Enviroment == Enviroment.Production)
                baseUri = new Uri(c_ProductionUri);
            else if(Enviroment == Enviroment.Sandbox)
                baseUri = new Uri(c_SandboxUri);
            else
                throw new InvalidOperationException("No URI for specified Enviroment.");

            Uri requestUri;
            Uri.TryCreate(baseUri, actionAddress, out requestUri);
            return (HttpWebRequest)HttpWebRequest.Create(requestUri);
        }
        #endregion
    }
}
